/*
 * LexicalAnalyzer.cpp
 *
 *  Created on: 12.10.2010
 *      Author: jilly
 */

#include <iostream>
#include <string>
#include <sstream>
#include "InputDataParser.h"
#include "../objects/BoardException.h"
#include "ParseException.h"

using namespace std;

InputDataParser::InputDataParser(const char* filename)
{
	this->filename = filename;
	init();
}

InputDataParser::~InputDataParser()
{
	terminate();
}

void InputDataParser::init()
{
	line = 1;
	chars = 0;
	tabs = 0;
	file = fopen(filename, "r");
	if (file == NULL)
	{
		stringstream ss;
		ss << "Can't open file " << filename;
		throw ParseException(ss.str().c_str());
	}
}

void InputDataParser::terminate()
{
	if (file != NULL) fclose(file);
}

void InputDataParser::dealloc()
{
	if (board != NULL) delete board;
}

char InputDataParser::nextSymbol()
{
	char c = getc(file);
	if (c != '\n' && c != EOF) lineBuffer += c;
	switch(c)
	{
		case ' ': chars++; return SYMB_SPACE;
		case '\r': return SYMB_CR;
		case '\n': line++; chars = 0; tabs = 0; lineBuffer.clear(); return SYMB_LF;
		case '\t': tabs++; return SYMB_TAB;
		case '\v': return SYMB_VTAB;
		case '\f': return SYMB_FF;
		case '@': chars++; return SYMB_AT;
		case 'B': chars++; return SYMB_B;
		case 'Q': chars++; return SYMB_Q;
		case 'X': chars++; return SYMB_X;
		case '#': chars++; return SYMB_HASH;
		case EOF: return SYMB_EOI;
		default: chars++; return c;
	}
}

/**
 * method parses input file
 *
 * input file may contain single line comments in format:
 * 		# this is single line comment
 * chess board definition format:
 * 		- 	before board definition there may be lines containing whitespace characters and
 * 			single line comments
 * 		-	after board definition there may be lines containing whitespace characters and
 * 			single line comments
 * 		-	there has to be border of chess-board made of characters 'X'
 * 		-	definition must start on new line (if there are some lines before actual definition),
 * 			no whitespace characters are allowed to be before 'X' character, so each line
 * 			with the definition must start with 'X' character
 * 		-	chess board must be at square at least 5x5 (we don't count boarders)
 * 		-	within boarder of board there are placed chess pieces (or nothing if there is no piece
 * 			on current position)
 * 		-	no whitespace characters (excluding space) are allowed to be within
 * 			borders of board
 * 		-	pieces are represented by these characters
 * 				'B'	- bishop
 * 				'Q'	- queen
 * 				'@'	- static piece (no matter what kind of)
 * 				' ' - (space) no piece, free position
 * 		-	there have to be exactly one bishop and one queen on board,
 * 			number of pieces placed on board must be within interval <n, (n*(n+1))/2>,
 * 			where n is size of chess board
 * 		-	after each line of board definition there may be whitespace characters and single
 * 			line comments
 * 		-	after board definition there may be additional lines containing whitespace characters
 * 			and single line comments
 *
 * 		EXAMPLE:
 *		----------------------------------------------------------------------------------------------
 * 						# this is comments
 * 						# i am alo comment, there are few whitespace characters before me
 *
 * 		# now we will define board
 * 		XXXXXXXXXXXX		# this is border, it contains 12 'X' characters so the board must be 12x12
 *		X    @ @ @ X	# first row of board, first 4 positions are free, then there's piece etc.
 *		X  @  @    X # another row
 *		X B        X # there is bishop on second position
 *		X          X # this row is empty - there are no pieces on it
 *		X          X # this also
 *		X @ @ @@@  X
 *		XQ         X # this is row 7, there is queen on first position
 *		X @ @ @    X
 *		X  @   @   X
 *		X @       @X # this is last row
 *		XXXXXXXXXXXX # and final boarder
 *
 *
 *		# i am the last thing in this file
 *		----------------------------------------------------------------------------------------------
 *
 *		AUTOMATON:
 *		(note: state ERR is error state, state OK is final state (input is parsed successfully))
 *
 *         ___________________________________________________S_Y_M_B_O_L_S_______________________________________________________________________________________________________________________________________
 *         |           LF              |           #              |            X                 | WHITESPACE - { LF }        | EOF                        |  B             |  ' '  |  @  |  Q             | OTHER
 *      ___|___________________________|__________________________|______________________________|____________________________|____________________________|________________|_______|_____|________________|______
 *	   | A |           A               |  B                       |  C   ( whitespaceChars == 0) |          A                 | ERR                        | ERR            |   A   | ERR | ERR            |  ERR
 *	   |   |                           |                          |  ERR ( whitespaceChars != 0) |                            |                            |                |       |     |                |
 *	 S | B |           A               |  B                       |            B                 |          B                 | ERR                        |  B             |   B   |  B  |  B             |   B
 *	   | C | F (count('x') >= 7)       | E (count('x') >= 7)      |            C                 | D (count('x') >= 7)        | ERR                        | ERR            |  ERR  | ERR | ERR            |  ERR
 *	 T |   | ERR (count('x') < 7)      | ERR (count('x') < 7)     |                              | ERR (count('x') < 7)       |                            |                |       |     |                |
 *	   | D |           F               |           E              |           ERR                |          D                 | ERR                        | ERR            |  ERR  | ERR | ERR            |  ERR
 *	 A | E |           F               |           E              |            E                 |          E                 | ERR                        |  E             |   E   |  E  |  E             |   E
 *	   | F |          ERR              |          ERR             |            G                 |         ERR                | ERR                        | ERR            |  ERR  | ERR | ERR            |  ERR
 *	 T | G |          ERR              |          ERR             |  H ( colsRead == boardSize)  |         ERR                | ERR                        | G (!bishopSet) |   G   |  G  | G (!queenSet)  |  ERR
 *	   |   |                           |                          |  ERR ( colsRead != boardSize)|                            |                            | ERR (bishopSet)|       |     | ERR (queenSet) |
 *	 E | H | F (linesRead < boardSize) |           I              |           ERR                |          H                 | ERR                        | ERR            |  ERR  | ERR | ERR            |  ERR
 *	   |   | J (linesRead == boardSize)|                          |                              |                            |                            |                |       |     |                |
 *	 S | I | F (linesRead < boardSize) |           I              |            I                 |          I                 | ERR                        |      I         |   I   |  I  |  I             |   I
 *	   |   | J (linesRead == boardSize)|                          |                              |                            |                            |                |       |     |                |
 *	   | J |          ERR              |          ERR             |            K                 |         ERR                | ERR                        | ERR            |  ERR  | ERR | ERR            |  ERR
 *	   | K | L (colsRead == boardSize) |M (colsRead == boardSize )| K (colsRead <= boardSize)    | L (colsRead == boardSize)  | ERR (colsRead != boardSize)| ERR            |  ERR  | ERR | ERR            |  ERR
 *	   |   | ERR(colsRead != boardSize)|ERR(colsRead != boardSize)| ERR (colsRead > boardSize)   | ERR (colsRead != boardSize)| OK (colsRead==boardSize)   |                |       |     |                |
 *	   | L |           L               |           M              | ERR                          |           L                | OK                         | ERR            |   L   | ERR | ERR            |  ERR
 *	   | M |           L               |           M              | M                            |           M                | OK                         | M              |   M   |  M  | M              |   M
 */
Board* InputDataParser::parse()
{
	/**
	 * boardSize
	 * 		determines size of board n, board must be n x n
	 * linesRead
	 * 		determines how many lines of board have we read yet (excluding top and bottom border)
	 * countWhitespaces
	 * 		counts whitespace symbols in the beginning of file. there may be 'infinite' whitespace
	 * 		characters but the line where definition of board starts must start with 'X' symbol
	 * 		and can't be preceded with whitespace characters
	 * colsRead
	 * 		determines how many columns on current line have we read from board definition
	 * 		(every line must have width = 'boardSize'
	 * countPieces
	 * 		counts pieces on board excluding queen and bishop, number of pieces must be within
	 * 		interval <minPieces, maxPieces> (minPieces = 'boardSize',
	 * 			maxPieces = [ 'boardSize' * ('boardSize' - 1) ] / 2;
	 * minPieces
	 * 		minimal number of pieces on board. is set after we read size of top border of
	 * 		board
	 * maxPieces
	 * 		maximum number of pieces on board. is set after we read size of top border of
	 * 		board
	 * queenSet
	 * 		determines if queen's position was set or not (for duplication)
	 * bishopSet
	 * 		determines if bishop's position was set or not (for duplication)
	 * state
	 * 		represents state of automaton
	 * symb
	 * 		currently read symbol
	 */
	unsigned int boardSize = 0, linesRead = 0, countWhitespace = 0, colsRead = 0, countPieces = 0,
			minPieces = 0, maxPieces = 0;
	bool queenSet = false, bishopSet = false;
	char symb;
	unsigned short state = STATE_A;


	cout << "Started parsing file " << filename << endl;

	do
	{
		symb = nextSymbol();
		switch (state)
		{
			case STATE_A:
				switch(symb)
				{
					case SYMB_HASH: state = STATE_B; break;
					case SYMB_X:
						if (countWhitespace == 0)
						{
							boardSize++;
							state = STATE_C;
						}
						else
							invalidSymbol(symb);
						break;
					default:
						if (isspace(symb))
						{
							if (symb == SYMB_LF)
								countWhitespace = 0;
							else
								countWhitespace++;
						}
						else
							invalidSymbol(symb);
						break;
				}
				break;

			case STATE_B:
				switch (symb) {
					case SYMB_LF:
						countWhitespace = 0;
						state = STATE_A;
						break;
					case SYMB_EOI: invalidSymbol(symb); break;
					default: break;
				}
				break;

			case STATE_C:
				switch(symb)
				{
					case SYMB_X: boardSize++; break;
					case SYMB_HASH:
						boardSize--; boardSize--; //don't count top corners (#)
						if (boardSize < 5)
						{
							dealloc();
							throw ParseException("Board size must be at least 5");
						}
						else
							minPieces = boardSize;
							maxPieces = (boardSize * (boardSize + 1)) / 2;
							state = STATE_E;
						break;
					case SYMB_LF:
						boardSize--; boardSize--; //don't count top corners (#)
						if (boardSize < 5)
						{
							dealloc();
							throw ParseException("Board size must be at least 5");
						}
						else
						{
							minPieces = boardSize;
							maxPieces = (boardSize * (boardSize + 1)) / 2;
							//create board object
							board = new Board(boardSize);
							state = STATE_F;
						}
						break;
					case SYMB_EOI: invalidSymbol(symb); break;
					default:
						if (isspace(symb))
						{
							boardSize--; boardSize--; //don't count top corners (#)
							if (boardSize < 5)
							{
								dealloc();
								throw ParseException("Board size must be at least 5");
							}
							else
							{
								minPieces = boardSize;
								maxPieces = (boardSize * (boardSize + 1)) / 2;
								state = STATE_D;
							}
						}
						else
							invalidSymbol(symb);
						break;
				}
				break; //END STATE_C

			case STATE_D:
				switch(symb)
				{
					case SYMB_HASH: state = STATE_E; break;
					case SYMB_LF:
						state = STATE_F;
						//create board object
						board = new Board(boardSize);
						break;
					case SYMB_EOI: invalidSymbol(symb); break;
					default:
						if (!isspace(symb)) invalidSymbol(symb);
				} //END switch(symb)
				break; //END STATE_D

			case STATE_E:
				if (symb == SYMB_EOI) invalidSymbol(symb);
				if (symb == SYMB_LF)
				{
					state = STATE_F;
					//create board object
					board = new Board(boardSize);
				}
				break;

			case STATE_F:
				if (symb == SYMB_X)
					state = STATE_G;
				else
					invalidSymbol(symb);
				break;

			case STATE_G:
				if (colsRead > boardSize)
				{
					stringstream ss;
					ss << "Row " << linesRead << " has " << colsRead << " columns but must have "
					<< boardSize << " columns" << endl;
					dealloc();
					throw ParseException(ss.str().c_str());
				}

				switch(symb)
				{
					case SYMB_Q:
						if (queenSet) {
							dealloc();
							throw ParseException("Duplicate queen found");
						}
						queenSet = true;
						try {
							board->placeQueen(colsRead, linesRead);
						} catch (BoardException e)
						{
							dealloc();
							throw ParseException(e.what());
						}
						//queen coordinates [colsRead,linesRead]
						colsRead++;
						break;
					case SYMB_B:
						if (bishopSet) {
							dealloc();
							throw ParseException("Duplicate bishop found!");
						}
						bishopSet = true;
						try {
							board->placeBishop(colsRead, linesRead);
						} catch (BoardException e)
						{
							dealloc();
							throw ParseException(e.what());
						}
						//bishop coordinates [colsRead,linesRead]
						colsRead++;
						break;
					case SYMB_SPACE: colsRead++; break;
					case SYMB_AT:
						//piece coordinate [colsRead, linesRead]
						//cout << "[" << colsRead << "," << linesRead << "]" << endl;
						try {
							board->addPiece(colsRead, linesRead);
						} catch (BoardException e)
						{
							dealloc();
							throw ParseException(e.what());
						}
						countPieces++;
						colsRead++;
						break;
					case SYMB_X:
						if (boardSize != colsRead)
						{
							stringstream ss;
							ss << "Row " << linesRead << " has " << colsRead << " columns but must have "
							<< boardSize << " columns" << endl;
							dealloc();
							throw ParseException(ss.str().c_str());
						}
						else
						{
							colsRead = 0;
							linesRead++;
							state = STATE_H;
						}
						break;
					default: invalidSymbol(symb);
				}
				break; //END STATE_G

			case STATE_H:
				switch (symb) {
					case SYMB_HASH: state = STATE_I; break;
					case SYMB_LF:
						if (linesRead < boardSize)
							state = STATE_F;
						else
						{
							if (countPieces < minPieces || countPieces > maxPieces)
							{
								stringstream ss;
								ss << "Number of pieces must be within " << minPieces << " and "
								<< maxPieces << " but it is " << countPieces << endl;
								dealloc();
								throw ParseException(ss.str().c_str());
							}

							if (!queenSet) {
								dealloc();
								throw ParseException("Queen is missing. Get queen on board!");
							}
							if (!bishopSet) {
								dealloc();
								throw ParseException("Bishop is missing. Get bishop on board!");
							}
							state = STATE_J;
						}
						break;
					case SYMB_EOI: invalidSymbol(symb); break;
					default:
						if (!isspace(symb)) invalidSymbol(symb);
						break;
				}
				break;
			case STATE_I:
				switch (symb) {
					case SYMB_LF:
						if (linesRead < boardSize)
						{
							state = STATE_F;
						}
						else
						{
							if (countPieces < minPieces || countPieces > maxPieces)
							{
								stringstream ss;
								cout << "Number of pieces must be within " << minPieces << " and "
								<< maxPieces << " but it is " << countPieces << endl;
								dealloc();
								throw ParseException(ss.str().c_str());
							}
							if (!queenSet) {
								dealloc();
								throw ParseException("Queen is missing. Get queen on board!");
							}
							if (!bishopSet) {
								dealloc();
								throw ParseException("Bishop is missing. Get bishop on board!");
							}
							state = STATE_J;
						}
						break;
					case SYMB_EOI: invalidSymbol(symb); break;
					default: break;
				}
				break;

			case STATE_J:
				if (symb == SYMB_X)
				{
					colsRead++;
					state = STATE_K;
				}
				else
					invalidSymbol(symb);
				break;

			case STATE_K:
				if (colsRead > (boardSize + 2)) //we were also counting bottom-left and bottom-right corner
				{
					dealloc();
					throw ParseException("----Invalid size of chessboard's low border");
				}

				switch (symb) {
					case SYMB_X: colsRead++; break;
					case SYMB_HASH:
						colsRead--; colsRead--; //substract bottom corners
						if (colsRead != boardSize)
						{
							stringstream ss;
							ss << "Invalid size of chessboard's low border. It's " << colsRead
							<< " and have to be " << boardSize;
							dealloc();
							throw ParseException(ss.str().c_str());
						}
						else
							state = STATE_M;
						break;
					case SYMB_EOI:
						colsRead--; colsRead--; //substract bottom corners
						if (colsRead != boardSize)
						{
								stringstream ss;
								ss << "Invalid size of chessboard's low border. It's " << colsRead
								<< " and have to be " << boardSize;
								dealloc();
								throw ParseException(ss.str().c_str());
						}
						break;
					default:
						if (isspace(symb))
						{
							colsRead--; colsRead--; //substract bottom corners
							if (colsRead != boardSize)
							{
								stringstream ss;
								ss << "Invalid size of chessboard's low border. It's " << colsRead
								<< " and have to be " << boardSize;
								dealloc();
								throw ParseException(ss.str().c_str());
							}
							else
								state = STATE_L;
						}
						else
							invalidSymbol(symb);
						break;
				}
				break;

			case STATE_L:
				if (symb == SYMB_EOI) break;
				if (!isspace(symb))
				{
					if (symb == SYMB_HASH)
						state = STATE_M;
					else
						invalidSymbol(symb);
				}
				break;

			case STATE_M:
				if (symb == SYMB_LF) state = STATE_L;
				if (symb == SYMB_EOI) break;
				break;

		} //END switch(state)
	} while (symb != SYMB_EOI);

	//FINAL CHECK
	switch (state) {
		case STATE_K:
		case STATE_L:
		case STATE_M:
			if (colsRead != boardSize)
			{
				stringstream ss;
				ss << "Invalid size of chessboard's low border. It's " << colsRead
				<< " and have to be " << boardSize;
				dealloc();
				throw ParseException(ss.str().c_str());
			}
			else
				cout << "Data file parsed successfully" << endl;
				cout << "Board size:		" << boardSize << "x" << boardSize << endl;
				cout << "Number of pieces:	" << board->getNumberOfPieces() << endl;
				cout << "Queen's position:	" << "[" << board->getQueen()->getX() << "," << board->getQueen()->getY() << "]" << endl;
				cout << "Bishop's position:	" << "[" << board->getBishop()->getX() << "," << board->getBishop()->getY() << "]" << endl << endl;
				return board;
			break;
		default: dealloc(); throw ParseException("Invalid input data"); break;
	}
}

void InputDataParser::invalidSymbol(char symb)
{
	string s;
	stringstream msg;
	switch(symb)
	{
		case SYMB_HASH:		s = "'#'"; break;
		case SYMB_AT: 		s = "'@'"; break;
		case SYMB_Q:		s = "'Q'"; break;
		case SYMB_B:		s = "'B'"; break;
		case SYMB_X:		s = "'X'"; break;
		case SYMB_SPACE:	s = "' '"; break;
		case SYMB_LF:		s = "'\n'"; break;
		case SYMB_CR:		s = "'\r'"; break;
		case SYMB_TAB:		s = "'\t'"; break;
		case SYMB_VTAB:		s = "'\v'"; break;
		case SYMB_FF:		s = "'\f'"; break;
		case SYMB_EOI:		s = "EOF"; break;
		default:			s = symb; break;
	}

	msg << lineBuffer << endl << indent(chars, tabs) << "^" << endl
	<< "Error, unexpected " << ((symb != SYMB_EOI) ? "character " : "") << s << " on line " << line << endl;
	dealloc();
	throw ParseException(msg.str().c_str());
}

string InputDataParser::indent(unsigned int countSpaces, unsigned int countTabs)
{
	stringstream ss;
	for (unsigned int i=0; i < countTabs; i++) ss << "\t";
	for (unsigned int i=0; i < countSpaces; i++) ss << " ";
	return ss.str();
}
